# Copyright 2007 pyPayPal Dev Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 only
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

from urllib import urlencode, unquote
from paypal_exceptions import *

class NVP_Response_Handler:
    """ Object for handling the response generated by Paypal NVP """
    def __init__(self):
        ## have to implement a check to ensure responce is a valid file object
        self.tokens = {}
        self.error_messages = []
        
    def parse_response(self, response):
        """ Parses the response into the dictionary self.tokens """
        uri_response = unquote(response.read())
        
        #split up the response
        for token in uri_response.split('&'):
            self.tokens[str(token.split("=")[0])] = str(token.split("=")[1])
        
        self.check_success()
        
    def _parse_errors(self):
        i = 0
        while 'L_LONGMESSAGE%s' %i in self.tokens:
            self.error_messages.append(self.tokens['L_LONGMESSAGE%s' %i])
            i += 1
        
    def check_success(self):
        if not self.tokens['ACK'] == 'Success':
            # Parse the error messages & pass them as an exception.
            self._parse_errors()
            if self.tokens['ACK'] == 'SuccessWithWarning':
                raise ResponseWarning, self.error_messages
            else:
                raise ResponseError, self.error_messages
            

class _Address:
    """ Base address class for interacting with Paypal NVP. """

    def __init__(self, first_name=None, last_name=None, address_line_one=None, address_line_two=None,
                 city=None, state=None, zipcode=None, country=None, country_code=None):
        """ Init class defaults. 
        Note:  If you need to use Providence, use the state value """
        self.first_name = first_name
        self.last_name = last_name
        self.address_line_one = address_line_one
        self.address_line_two = address_line_two
        self.city = city
        self.state = state              #State and Providence are the same to paypal.
        self.zipcode = zipcode
        #self.providence = providence
        self.country = country
        self.country_code = country_code
        
    def is_valid(self):
        """ Raise AddressError if all of the required fields have not been validated. """
        # We most likely want to add some validators here, rather then just testing if the
        # data is present
        if not (self.first_name and self.last_name):
            raise IncompleteObjectError, 'Missing first and/or last name'
        if not (self.address_line_one):
            raise IncompleteObjectError, 'Missing Address'
        if not (self.city and self.state and self.zipcode):
            raise IncompleteObjectError, 'Missing city and/or state and/or zip'
        if not (self.country and self.country_code):
            raise IncompleteObjectError, 'Missing country and/or country code'

class Billing_Address(_Address):
    """ A billing address class. """

    def get_uri(self):
        """ Return an URI string representing a billing address for Paypal NVP. """
        params = {
            'FIRSTNAME': self.first_name,
            'LASTNAME': self.last_name,
            'CITY': self.city,
            'STATE': self.state,
            'ZIP': self.zipcode,
            'COUNTRY' : self.country,
            'COUNTRYCODE': self.country_code,
        }
        if self.address_line_two:
            params['STREET'] = "%s %s" % (self.address_line_one, self.address_line_two)
        else:
            params['STREET'] = "%s" % (self.address_line_one)
            
        return urlencode(params)

class Shipping_Address(_Address):
    """ A shipping address class. """
    
    def get_uri(self):
        """ Return an URI string representing a shipping address for Paypal NVP. """
        
        params = {
        'SHIPTONAME' : "%s %s" % (self.first_name, self.last_name),
        'SHIPTOSTREET' : self.address_line_one,
        'SHIPTOCITY' : self.city,
        'SHIPTOSTATE' : self.state,
        'SHIPTOZIP' : self.zipcode,
        'SHIPTOCOUNTRYCODE' : self.country_code,
        }
        
        #If there is a second part of the address, make sure its included
        if(self.address_line_two): params['SHIPTOSTREET2'] = self.address_line_two
            
        return urlencode(params)

class Credit_Card:
    """ Credit Card class for working with Paypal NVP. """

    def __init__(self, account=None, expiration_date=None, cvv2=None, type=None):
        """ Initialize the class defaults. """
        self.account = account
        self.expiration_date = expiration_date
        self.cvv2 = cvv2
        self.type = type

    def is_valid(self):
        """ Raise CardError if data is not validated. """
        if not (self.account and
                self.expiration_date and
                self.cvv2 and
                self.type):
            raise IncompleteObjectError, 'Incomplete CreditCard Object.'
        else: pass

    def get_uri(self):
        """ Returns an URI string representing a credit card for Paypal NVP. """
        params = {
        'ACCT': self.account,
        'EXPDATE' : self.expiration_date,
        'CVV2' : self.cvv2,
        'CREDITCARDTYPE': self.type,
        }
        
        return urlencode(params)

class Bank_Account:
    """ Bank account class for NVP.  Currently NOT SUPPORTED"""
    def __init__(self):
        self.routing_number = None
        self.account_number = None
        self.drivers_licence = None
        # I have no idea what need to go here as of yet.  FIX ME!

    def is_valid(self):
        return 0

    def get_uri(self):
        return ""
        
class Item:
    """
    Item class for giving paypal users specific details about each item on their paypal account
    Note:  Both amount and quantity are with respect to ONE ITEM.  Quantity will act as the multiplier when used with other objects (such as cart)
    eg. Item1
        amount = 4.00
        tax = 1.00
        quantity = 3,
        
        then...
        subtotal = 12.00
        tax = 3.00
        amount = 15.00
    """
    def __init__(self, name=None, quantity=1, amount=0, description=None, item_number=None, tax=0):
        self.name = name
        self.quantity = quantity
        self.amount = amount
        self.description = description
        self.item_number = item_number
        self.tax = tax

    def is_valid(self):
        """ Raise IncompleteObjectError if data is not validated. """
        
        if not (self.quantity and 
                self.name and 
                self.amount and
                self.tax
                ):
            raise IncompleteObjectError, 'Item %s is not complete.'% self.name
        else: pass

    def get_uri(self, indx):
        """ Returns the URI item for PAYPAL NVP.  Number is the item index number, starting with 0 """
        params = {
        'L_QTY%s' % indx : self.quantity,
        'L_NAME%s' % indx : self.name,
        'L_AMT%s' % indx : self.amount,
        'L_TAXAMT%s' % indx : self.tax,
        }
        if self.description:
            params['L_DESC%s' % indx] = self.description
            
        if self.item_number:
            params['L_NUMBER%s' % indx] = self.item_number

        return urlencode(params)
        
class Cart:
    """ 
    Add items to the class to conveniently manage a single cart.
    If you use this class, you must also specify a Subtotal object.  This be made easier by linking the shoppers cart to the Subtotal object.
    You can then call the calculate functions within subtoal to set the appropriate values.  If you do not include a Subtotal object,
    Paypal will be angry with you and haunt you while you sleep.  In reality though, it will return a SuccessWithErrors.
    """
    def __init__(self, cart = []):
        """
        You can take an existing cart and add it to a new cart if you like
        """
        self.cart = cart
        
    def add_item(self,item):
        """
        Add NVPItems to the cart
        """
        if isinstance(item, Item): 
            self.cart.append(item)
        else:
            raise InvalidParamaterError,'You can only add item objects to the shopping cart.'
        
    def get_subtotal(self):
        """
        Returns the subtotal of all the items in the cart
        """
        total = 0
        for item in self.cart:
            total += item.amount * item.quantity
        return total
        
    def get_tax(self):
        """
        Reurns the tax of items in the cart
        """
        tax = 0
        for item in self.cart:
            tax += item.tax * item.quantity
        return tax
    
    def is_valid(self):
        """ Error Check Class """
        for item in self.cart:
            item.is_valid()
    
    def get_uri(self):
        """ Returns the NVP URI """
        cart_uri=[]
        indx = 0
        for item in self.cart:
            # We can't assume this unless we generate the indx
            cart_uri.append(item.get_uri(indx))
            indx += 1
        cart_uri = '&'.join(cart_uri)

        return cart_uri
            
class Subtotal:
    """ Subtotal class for NVP
        PayPal requires you to set subtotal and tax if you specify items.  The sum of the listed item's values must equal the subtotal.
        the sum of the listed item's tax values must be equal to the tax value.  If either or both of these are not equal, then paypal will 
        not list the items, and return a success with warning.
        You have to set shipping and handling manually
        Feature:  You can either specify the amount and tax values yourself, or give the class a shopping cart and have
        it automatically calculate the amount and tax values for you. (Note: DO NOT CHANGE subtotal or tax if you want this feature to work)  """
    def __init__(self, subtotal=0, tax=0, shipping=0, handling=0, cart=None):
        self.subtotal = subtotal
        self.tax = tax
        self.shipping = shipping
        self.handling = handling
        self.cart = cart 
    
    def get_total(self):
        """ returns the total for amount.  Useful in the DirectPayment class """
        return self.subtotal + self.tax + self.shipping + self.handling
        
    def calculate_subtotal(self):
        """ 
        Sums up and sets the subtotal based on the items in the cart.
        """
        if self.cart:
            self.subtotal = self.cart.get_subtotal()
        else:
            raise MissingObjectError,'You must first specify a cart'
            
    def calculate_tax(self):
        """ 
        Sums up and sets the tax based on the items in the cart.
        """
        if self.cart:
            self.tax = self.cart.get_tax()
        else:
            raise MissingObjectError,'You must first specify a cart'
        
    def is_valid(self):
        """
        Validation Function.  Required.
        """ 
        if not (self.subtotal and  
                self.tax #and 
                #self.shipping and
                #self.handling
                ):
            raise IncompleteObjectError, 'Subtotal is not complete.'
        else: pass

            
    def get_uri(self):
        """
        Returns the values in NVP format, and those of the cart, if it exists
        """
        params = {'ITEMAMT' : self.amount,
                  'TAXAMT'  : self.tax
                  }
        if self.shipping:
            params['SHIPPINGAMT'] = self.shipping
        if self.handling:
            params['HANDLINGAMT'] = self.handling
        
        uri = urlencode(params)
        return uri